#!/bin/env python

"""Top-level module for the Dingo GUI.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA


This module contains the GUI methods for creating and running a Dingo
application.

The GUI consists of the following parts:

 1. Menu bar:
   - Project: Allows file handling functions such as creating a new
     simulation, Loading and Saving Simulations.
   - Edit: Cut / Copy / Paste for text selections
   - Simulation: controls the running of simulations, Starting,
     Stopping and pausing simulations.
   - Help: Documentation and an About box

 2. Tree Controller: [NOT VISIBLE FOR NOW]
   - Lists the directory structure of the current project. It is a
     wx.TreeCtrl object

 3. Network Graph:
   - A plan view of the sensor network. Shows the relative positions
     of each of the sensors and displays communications between
     sensors. There are many visual modifications to the data
     displayed here such as sensor colour that can be made during a
     simulation. See the L{Dingo.OGL.SensorGraph} for details

 4. Code Editor:
   - Styled Python code editor.

Dingo.Main is based on Robin Dunn's text editor from the wxPython
examples, which was (c) 1999 by Total Control Software under the
wxWindows licence.
"""

__author__ = 'John Perks, Sarah Mount'
__credits__ = "Based on Robin Dunn's text editor demo from the wxPython examples, (c) 1999 by Total Control Software under the wxWindows license"

_debug = False

import sys, os, time, pdb, gc, string, threading, imp
import wx, wx.html as html
import wx.lib.dialogs

import Dingo
import Dingo.OGL as OGL
import Dingo.editor as editor
import Dingo.locks as locks
import Dingo.simulation as simulation
import Dingo.pluginManager as pluginManager
import Dingo.consts as consts

from Dingo.pubsub import Publisher # ??

import psyco # need to profile this...
psyco.full()

_appName = 'Dingo'

_fileTypes = {
        '.py': 'Python files',
        '.java': 'Java files',
        '.txt': 'Text files'}
"""var _fileType: list of file types to include in the project tree in
the GUI.
"""

_idxDefault = 0
_idxCustom = 1

_defaultMain ="""#!/usr/bin/env python
import Dingo.API as api
import Dingo.models.FloodRouter as FloodRouter
import random


class TheSimulation(api.Simulation):
    def __init__(self, graph):
        ether = api.Ether(self)
        num_nodes = 20
        sensors = (MySensor(ether, i, num_nodes) for i in xrange(num_nodes))
        api.Simulation.__init__(self, sensors, graph)
    def run(self):
        super(TheSimulation, self).run()
    
class MySensor(api.Sensor):
    def __init__(self, ether, i, num_nodes):
        x = random.randrange(400)
        y = random.randrange(500)
        api.Sensor.__init__(self, "S%d" % i, (x, y), api.SensorColor.Grey, 
                api.Scheduler([MyTask(self, num_nodes)], -1), 
                ether, # STILL NEED THIS BUT WILL SOON BE DEPRECATED
                FloodRouter.FloodRouter())
        return

class MyTask(api.Task): #A task the sensors preform
    def __init__(self, sensor, num_nodes):
        # self, priority, repetitions, ref-to-sensor
        api.Task.__init__(self, 0, 0, sensor)
    def task(self):
        # Change colour randomly
        rgb = random.randrange(1<<24)
        self.sensor.setColour(api.SensorColor.FromRGBCombined(rgb))

        # Your Code for each task goes here....

        sleeptime = int(random.uniform(1,4)) # Pause the sim so we can see what is happening
        api.Sleep(sleeptime)
"""
"""@var: The code template loaded when a new Dingo application is created.
@type: str
"""

_defaultsMap = { 'main.py' : _defaultMain }

#---------------------------------------------------------------------------

def opj(path):
        """Convert paths to the platform-specific separator

	@type path: str
	@param path: Path string to clean up. 
	@rtype str
	"""
        return os.path.join(*path.split('/'))

#---------------------------------------------------------------------------


class DingoApp(wx.Frame):
        """       
        The main class for the Dingo application.
        This builds the application GUI, creating each separate component and attaching it to the main UI window.
        """
        def __init__(self, parent, id, title):
                """Build the application and attach its components"""
                wx.Frame.__init__(self, parent, -1, title, size = (800, 600),
                                                  style=wx.DEFAULT_FRAME_STYLE |
                                                  wx.NO_FULL_REPAINT_ON_RESIZE)

                self.cwd = os.getcwd()
                self.window = None
                self.sim = None

		myfilename = (Dingo.Main.__file__).split(os.sep)
		mydir = myfilename[:len(myfilename)-1]

		icon = wx.Icon(string.join(mydir,'/')+'/images/favicon.ico',
			       wx.BITMAP_TYPE_ICO)
		self.SetIcon(icon)

		# Helps us know if options should be greyed out, etc
                self.needFiles = []
                self.needCode = []
                self.needSelection = []
                self.needRunning = []
                self.needPaused = []
                self.needStopped = []
                self.needFilesNotStopping = []
                self.needCodeNotStopping = []


                self.Bind(wx.EVT_IDLE, self.OnIdle)
                self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
                self.Bind(wx.EVT_ICONIZE, self.OnIconfiy)
                self.Bind(wx.EVT_MAXIMIZE, self.OnMaximize)

                self.Centre(wx.BOTH)
                self.status = self.CreateStatusBar(1, wx.ST_SIZEGRIP)
                # apply sizer

                # Split panes
#                splitter3 = wx.SplitterWindow(self, -1)
#                splitter  = wx.SplitterWindow(splitter3, -1)
                splitter  = wx.SplitterWindow(self, -1)

                def EmptyHandler(evt): pass
                splitter.Bind(wx.EVT_ERASE_BACKGROUND, EmptyHandler)
#                splitter3.Bind(wx.EVT_ERASE_BACKGROUND, EmptyHandler)

                # Prevent TreeCtrl from displaying all items
                # after destruction when True
                self.dying = False
                
                #Dir for project
                self.projectDir = None

                # Create a TreeCtrl to house the directory structure of the Project
                # The PyData for each file in the Tree will be a (path, pageNum) pair
                tID = wx.NewId()
                self.treeMap = {}
                self.tree = wx.TreeCtrl(splitter, tID, style = wx.TR_DEFAULT_STYLE
                                                                #| wx.TR_HAS_VARIABLE_ROW_HEIGHT
                                                           )
		self.tree.SetMinSize((100, 0)) # ??


                # Create a Notebook to display the files
#                self.nb = wx.Notebook(splitter3, -1, style=wx.CLIP_CHILDREN)
                self.nb = wx.Notebook(splitter, -1, style=wx.CLIP_CHILDREN)
                self.EnableDisableForFiles()

                # Sensor canvas
                self.canvas = OGL.SensorGraph(splitter, self)

                # add the windows to the splitter and split it.
                splitter.SplitVertically(self.canvas, self.nb)		
#                splitter.SplitVertically(self.tree, self.canvas)
#                splitter3.SplitVertically(splitter, self.nb)

                # Make the splitter on the right expand the top window when resized
                def SplitterOnSize(evt):
                        splitter = evt.GetEventObject()
                        sz = splitter.GetSize()
                        splitter.SetSashPosition(sz.height - 120, False)
                        evt.Skip()

		### Deal with plugins
		self.pluginManager = pluginManager.PluginManager(self.canvas, self.nb, self.status)

		### Create menu -- requires plugins to be loaded

                # Make a menubar for Dingo
                self.mainmenu = wx.MenuBar()

                # The Project menu
                menu = wx.Menu()
                item = menu.Append(-1, '&New Project...\tCtrl-N', 'Create new Dingo project')
                self.Bind(wx.EVT_MENU, self.OnNewProject, item)
                item = menu.Append(-1, '&Open...\tCtrl-O', 'Open Project')
                self.Bind(wx.EVT_MENU, self.OnProjectOpen, item)        
                item = menu.Append(-1, '&Save\tCtrl-S', 'Save current project')
                self.needFiles.append(item)
                self.Bind(wx.EVT_MENU, self.OnProjectSave, item)
                item = menu.Append(-1, '&Close\tCtrl-W', 'Close current project')
                self.needFiles.append(item)
                self.Bind(wx.EVT_MENU, self.OnProjectClose, item)
                menu.AppendSeparator()
                item = menu.Append(-1, 'Add file', 'Create new file in current project')
                self.needFiles.append(item)
                self.Bind(wx.EVT_MENU, self.OnAddFile, item)
                item = menu.Append(-1, 'Add directory', 'Create new directory in current project')
                self.needFiles.append(item)
                self.Bind(wx.EVT_MENU, self.OnAddDir, item)
                menu.AppendSeparator()
                item = menu.Append(-1, 'E&xit\tAlt-X', 'Exit Dingo')
                self.Bind(wx.EVT_MENU, self.OnProjectExit, item)
                wx.App_SetMacExitMenuItemId(item.GetId())

                self.mainmenu.Append(menu, '&Project')


                # The Edit menu
                menu = wx.Menu()
                item = menu.Append(-1, "Cu&t\tCtrl-X", 'Cut selected text')
                self.needSelection.append(item)
                self.Bind(wx.EVT_MENU, self.OnEditCut, item)
                item = menu.Append(-1, '&Copy\tCtrl-C', 'Copy selcted text')
                self.needSelection.append(item)
                self.Bind(wx.EVT_MENU, self.OnEditCopy, item)
                item = menu.Append(-1, '&Paste\tCtrl-V', 'Paste over selection')
                self.Bind(wx.EVT_MENU, self.OnEditPaste, item)
#                self.menuPaste = item

                self.mainmenu.Append(menu, '&Edit')


		# View menu
#  		menu = wx.Menu()
# 		item = menu.Append(wx.ID_ANY, "&Topology graph",
# 				   'View network topology',
# 				   kind=wx.ITEM_CHECK)
# 		self.needCode.append(item)
# 		item.Check(False)
#                 self.Bind(wx.EVT_MENU, self.OnViewTopology, item)
# 		self.top_page = None # Notebook page for network topology

# 		self.mainmenu.Append(menu, '&View')


                # The Application menu (formerly simulation menu)
                menu = wx.Menu()
                item = menu.Append(-1, '(Re)&load code', 'Reload project code into Dingo')
                self.needFilesNotStopping.append(item)
                self.Bind(wx.EVT_MENU, self.OnLoadCode, item)
                item = menu.Append(-1, 'Reset &Graph', 'Reset the network pane as if the project had just been loaded')
                self.needCodeNotStopping.append(item)
                self.Bind(wx.EVT_MENU, self.OnResetGraph, item)
                item = menu.Append(-1, '(Re)&start', 'Start running the current Dingo project')
                self.needCodeNotStopping.append(item)
                self.Bind(wx.EVT_MENU, self.OnStart, item)
                item = menu.Append(-1, '&Pause', 'Pause the current Dingo project')
                self.needRunning.append(item)
                self.Bind(wx.EVT_MENU, self.OnPause, item)
                item = menu.Append(-1, '&Resume', 'Resume running the current Dingo project')
                self.needPaused.append(item)
                self.Bind(wx.EVT_MENU, self.OnResume, item)

		item = menu.Append(-1, '&Attach', 'Attach GUI to running Dingo network')
                self.needRunning.append(item)
                self.Bind(wx.EVT_MENU, self.OnAttach, item)
		item = menu.Append(-1, '&Detach', 'Detach GUI from running Dingo network')
                self.needRunning.append(item)
                self.Bind(wx.EVT_MENU, self.OnDetach, item)
		

                self.mainmenu.Append(menu, '&Simulation')


		# Network topology menu
		menu = wx.Menu()
		item = menu.Append(-1, 'Random (&Uniform)', 'Randomise network topology using a uniform distribution')
                self.needCode.append(item)
		self.Bind(wx.EVT_MENU, self.OnRandomiseTopology, item)
		item = menu.Append(-1, '&Random (Gaussian)', 'Randomise network topology using a Gaussian distribution')
                self.needCode.append(item)
		self.Bind(wx.EVT_MENU, self.OnGaussianiseTopology, item) 
		
 		item = menu.Append(-1, '&Grid', 'Gridify network topology')
                self.needCode.append(item)
		self.Bind(wx.EVT_MENU, self.OnGridifyTopology, item)

		menu.AppendSeparator()		
 		item = menu.Append(-1, '&LoadFromFile', 'Load a network topology from a CSV file')
                self.needCode.append(item)
		self.Bind(wx.EVT_MENU, self.OnLoadTopology, item)
 		item = menu.Append(-1, '&SaveToFile', 'Save current network topology from a CSV file')
                self.needCode.append(item)
		self.Bind(wx.EVT_MENU, self.OnSaveTopology, item)

		self.mainmenu.Append(menu, '&Topology')


		# Logging menu
		menu = wx.Menu()
		self.isLoggingTop = menu.Append(1001, '&Topology', 'Log network topology to CSV file', kind=wx.ITEM_CHECK)
		menu.Check(1001, True)
		self.isLoggingPackets = menu.Append(1002, '&Packets', 'Log all radio packets sent during project execution', kind=wx.ITEM_CHECK)
        	menu.Check(1002, True)
                self.needCode.append(self.isLoggingPackets)
		self.Bind(wx.EVT_MENU, self.OnPacketLoggingToggle, id=1002)

		self.mainmenu.Append(menu, '&Logging')


                # Plugin menu
                self.pluginMenu = wx.Menu()
		
		item = self.pluginMenu.Append(wx.ID_ANY, '&Activate all', 'Activate all available plugins')
		self.Bind(wx.EVT_MENU, self.OnActivateAllPlugins, item)

		item = self.pluginMenu.Append(wx.ID_ANY, '&Deactivate all', 'Deactivate all available plugins')
		self.Bind(wx.EVT_MENU, self.OnDeactivateAllPlugins, item)
		
		self.pluginMenu.AppendSeparator()
		ids = {}
		for plugin in self.pluginManager.getPluginNames():
			item = self.pluginMenu.Append(wx.ID_ANY, plugin,
						      kind=wx.ITEM_CHECK)
			self.pluginMenu.Check(item.GetId(), False)
			self.Bind(wx.EVT_MENU, self.OnPluginToggle, item)
			ids[plugin] = item.GetId()

		self.mainmenu.Append(self.pluginMenu, 'Plu&gins')
		self.pluginManager.setMenu(self.pluginMenu, ids)
		

                # Make a Help menu
                menu = wx.Menu()
                item = menu.Append(-1, '&Contents\tCtrl-H', 'See contents page for Dingo help')
                self.Bind(wx.EVT_MENU, self.OnHelpContents, item)
                item = menu.Append(-1, '&Index', 'See index page for Dingo help')
                self.Bind(wx.EVT_MENU, self.OnHelpIndex, item)
                menu.AppendSeparator()
                item = menu.Append(-1, '&About', 'All about Dingo')
                self.Bind(wx.EVT_MENU, self.OnHelpAbout, item)
                self.help = None

                self.mainmenu.Append(menu, '&Help')
                self.SetMenuBar(self.mainmenu)

                if not _file == False:
                        print "Attempting To Load Command line parameter"
                        try:
                                self.OpenProject(_file)
                        except:
                                print "Unable to open %s " %_file

        #---------------------------------------------
        def OnSelChanged(self, event):
                """Event called when the selection has changed.
                """
                if self.dying:
                        return

                item = event.GetItem()
                itemText = self.tree.GetItemText(item)

        def EnableDisableForFiles(self):
                """Enables or disables controls depending on state of the application"""
                hasFiles = (self.nb.GetPageCount() > 0)
                for item in self.needFiles:
                        item.Enable(bool(hasFiles))
                
                hasSelection = False
                pageNum = self.nb.GetSelection()
                if pageNum >= 0:
                        hasSelection = self.nb.GetPage(pageNum).GetSelectedText()
                for item in self.needSelection:
                        item.Enable(bool(hasSelection))

                import Dingo.Project as Project
                hasCode = hasFiles and self.sim is not None
                for item in self.needCode:
                        item.Enable(bool(hasCode))

                isRunning = locks.SimRunning()
                isPaused = locks.SimPaused()
                isStopped = threading.activeCount() <= 1
                isStopping = not(isRunning or isPaused or isStopped)

                for item in self.needRunning:
                        item.Enable(bool(isRunning))
                for item in self.needPaused:
                        item.Enable(bool(isPaused))
                for item in self.needStopped:
                        item.Enable(bool(isStopped))
                for item in self.needFilesNotStopping:
                        item.Enable(bool(hasFiles and not isStopping))
                for item in self.needCodeNotStopping:
                        item.Enable(bool(hasCode and not isStopping))


        #---------------------------------------------
        # Menu methods
        def OnProjectExit(self, event=None):
                """Event called when we wish exit the app.
                Called from the 'close project' command on the UI's menu-bar this will close the current project
                """
                try:
                        self.ClearOldCode()
                finally:
                        self.Close()

        def OnProjectOpen(self, event=None):
                """Event to open an existing Project from disk.
                Called from the load project command in the menu-bar, this loads a previously saved project
                """
                try:
                        self.OpenProject()
                finally:
                        self.EnableDisableForFiles()

        def OpenProject(self, path=None):
                """Opens a project, and populates the Tree and Notebook
                A Helper method called by the L{Dingo.Main.DingoApp.OnProjectOpen} callback. 
                """
                if _debug: pdb.set_trace()
                if not path:
                        dlg = wx.DirDialog(self, defaultPath=os.getcwd())
                        if dlg.ShowModal() != wx.ID_OK:
                                return
                        path = os.path.abspath(dlg.GetPath())
                        print "Default Path %s" %os.getcwd()
                        print "Path %s" %path

                self.ClearOldCode()
                self.CloseProject() # clear out current pages etc

                self.NewTree(path)
                # Walk the directories, building the Tree and adding files to Notebook
                for (root, dirs, files) in os.walk(path):
                        for d in dirs:
                                d = os.path.join(root, d)
                                d = os.path.abspath(d)
                                self.AddDirToTree(d)

                        for (fullName, name) in [(os.path.join(root, name), name) for name in files]:
                                fullName = os.path.abspath(fullName)
                                ext = os.path.splitext(name)[1].lower()
                                if ext not in _fileTypes: continue

                                f = open(fullName, 'r')
                                try:
                                   data = f.read()
                                finally:
                                   f.close(); f = None
                                self.AddFile(fullName, data)

                if self.nb.GetPageCount():
                        self.nb.SetSelection(0)
                        self.projectDir = path
                else:
                        wx.MessageBox("The directory tree at %s contains no editable files!" % path,
                                _appName, wx.ICON_ERROR)
                        self.OnProjectClose()


        def OnProjectClose(self, event=None):
                """Event to close currently open project"""
                try:
                        self.ClearOldCode()
                        self.CloseProject()
                finally:
                        self.EnableDisableForFiles()

        def CloseProject(self):
                """ Helper method to close the current project. 
                This method is called by the L{Dingo.Main.DingoApp.OnProjectClose} callback.
                It triggers a message box confirming the project is to be closed.  If so
                the project is removed from the UI and memory
		"""
                if _debug: pdb.set_trace()
		self.pluginManager.deactivateAllPlugins()
                if self.nb.GetPageCount():
                        if wx.MessageBox("Do you wish to save current project?",
                                _appName, style=wx.YES_NO) in (wx.YES, wx.ID_YES):
                                self.SaveProject()
		for item in self.pluginMenu.GetMenuItems():
			if item.IsChecked():
				item.Check(False)
                self.nb.DeleteAllPages()
                self.projectDir = None
                self.ClearTree()

        def OnProjectSave(self, event=None):
                """Event called to save project.
                Triggered by the UI to save the project.
		Calls the #{Dingo.Main.DingoApp.SaveProject} method
		"""
                self.SaveProject()

        def SaveProject(self):
                """Helper Method to save project."""
                if _debug: pdb.set_trace()
                self.SaveTree(self.tree.GetRootItem())
        
        def SaveTree(self, item):
                """Recursively save the project tree.
                Recursively walk the tree, saving files as we go. 
		Called by the L{Dingo.Main.DingoApp.OnProjectSave} callback.
		"""
                t = self.tree
                if item != t.GetRootItem():
                        fullPath, pageNum = t.GetPyData(item)
                        if pageNum >= 0 and pageNum < self.nb.GetPageCount():
                                page = self.nb.GetPage(pageNum)
                                pageText = self.nb.GetPageText(pageNum)
                                data = page.GetText()
                                f = open(fullPath, 'w')
                                try:
                                        f.write(data)
                                finally:
                                        f.close(); f = None
                child, cookie = t.GetFirstChild(item)
                while child.IsOk():
                        self.SaveTree(child)
                        child = t.GetNextSibling(child)


        def ClearTree(self):
                """ Removes all items from the project Tree"""
                self.tree.DeleteAllItems()

        def NewTree(self, path):
                """Creates a new project Tree"""
                self.ClearTree()
                path = os.path.normcase(os.path.abspath(path))
                projName = os.path.split(path)[1]
                t = self.tree
                root = t.AddRoot("Project %s" % projName)
                t.SetPyData(root, path)
                itemData = (t.GetPyData(root), -1)
                defaults = t.AppendItem(root, "Default files")
                t.SetPyData(defaults, itemData)
                customs = t.AppendItem(root, "Custom files")
                t.SetPyData(customs, itemData)
                
        def FixupTreeDir(self, path):
                """Create tree entry for Dir if not present, and return it"""
                folder = self.GetFolder(_idxCustom)
                t = self.tree

                for d in path.split(os.path.sep):
                        if not d: continue

                        parentPath, pageNum = t.GetPyData(folder)
                        if pageNum >= 0: continue # not a dir

                        child, cookie = t.GetFirstChild(folder)
                        while child.IsOk():
                                if d == t.GetItemText(child):
                                        break
                                child = t.GetNextSibling(child)
                        else:
                                # Got to end, no match, so make one
                                parentPath, pageNum = t.GetPyData(folder)
                                child = t.AppendItem(folder, d)
                                t.SetPyData(child,
                                        (os.path.join(parentPath, d), -1))
                                t.SortChildren(folder)
                        folder = child
                return folder

        def FixupPath(self, path):
                """Return path relative to Project Dir"""
                path = os.path.normcase(os.path.abspath(path))
                root = self.tree.GetRootItem()
                assert root is not None and root.IsOk()
                rootPath = self.tree.GetPyData(root)
                assert path.startswith(rootPath)

                path = path[len(rootPath):]
                while path.startswith(os.path.sep):
                        path = path[len(os.path.sep):]
                return path

        def AddDirToTree(self, path):
                """Adds a directory to the project tree"""
                path = self.FixupPath(path)
                self.FixupTreeDir(path)
                        
        def AddFile(self, path, data):
                """Add a single file to Tree and Notebook"""
                if _debug: pdb.set_trace()
                path = self.FixupPath(path)
                folderPath, fname = os.path.split(path)

                if (not folderPath) and fname in _defaultsMap:
                        folder = self.GetFolder(_idxDefault)
                else:
                        folder = self.FixupTreeDir(folderPath)
                t = self.tree
                rootPath = t.GetPyData(t.GetRootItem())
                parentPath, parentPageNum = t.GetPyData(folder)
                assert parentPageNum < 0

                page = editor.PythonCodeEditor(self.nb)
                page.SetValue(data)
                pageNum = self.nb.GetPageCount()
                self.nb.AddPage(page, path)
                treeItemData = (os.path.join(rootPath, path), pageNum)
                newItem = t.AppendItem(folder, fname)
                t.SetPyData(newItem, treeItemData)
                t.SortChildren(folder)
                        

        def GetFolder(self, idx):
                """Returns the idx'th subfolder from the Tree root"""
                root = self.tree.GetRootItem()
                assert root is not None and root.IsOk()
                if idx >= self.tree.GetChildrenCount(root, False):
                        raise IndexError(`idx`)
                item, cookie = self.tree.GetFirstChild(root)
                while idx:
                        idx -= 1
                        item = self.tree.GetNextSibling(item)
                        assert item is not None and item.IsOk()
                return item

        def OnNewProject(self, event=None):
                """Create a new project with default files"""
                if _debug: pdb.set_trace()
                dlg = wx.TextEntryDialog(self, "Please enter name of new project", "New Project")
                try:
                        if dlg.ShowModal() != wx.ID_OK:
                                return
                        projName = dlg.GetValue().strip()
                finally:
                        dlg.Destroy(); dlg = None
                if not projName or os.path.dirname(projName):
                        wx.MessageBox("Invalid Project Name!", _appName, style=wx.ICON_ERROR)

                dlg = wx.DirDialog(self, "Where to locate new project directory?",
                        defaultPath = os.getcwd())
                try:
                        if dlg.ShowModal() != wx.ID_OK:
                                return
                        os.chdir(dlg.GetPath())
                        path = os.path.abspath(os.path.join(dlg.GetPath(), projName))
                finally:
                        dlg.Destroy(); dlg = None

                if os.path.exists(path):
                        wx.MessageBox("Directory %s already exists" % projName, _appName, style=wx.ICON_ERROR)
                        return
                os.mkdir(path)
                for fname, text in _defaultsMap.iteritems():
                        fname = os.path.join(path, fname)
                        f = open(fname, 'w')
                        try:
                                f.write(text)
                        finally:
                                f.close()
                self.OpenProject(path)

        def OnIdle(self, event):
                """Idle processing, check clipboard and running thread count"""
                if self.dying:
                        return
                self.CheckClipboard()
                self.EnableDisableForFiles()
                count = threading.activeCount() - 1
                if count:
                        self.status.SetStatusText("%s threads still active" % count)
                else:
                        self.status.SetStatusText('')

        def CheckClipboard(self):
                """Check clipboard for files.
                This checks the clipboard for files, if anything exists enables the UI paste function.
		"""
                canPaste = False
                pageNum = self.nb.GetSelection()
                if pageNum >= 0:
                        page = self.nb.GetPage(pageNum)
                        canPaste = page.CanPaste()
#                self.menuPaste.Enable(canPaste)


        def OnEditCut(self, event=None):
                """Preforms the Cut clipboard event."""
                self.nb.GetPage(self.nb.GetSelection()).Cut()
                        
        def OnEditCopy(self, event=None):
                """Preforms a Copy clipboard event."""
                self.nb.GetPage(self.nb.GetSelection()).Copy()
                        
        def OnEditPaste(self, event=None):
                """Preforms a paste clipboard event."""
                self.nb.GetPage(self.nb.GetSelection()).Paste()

        def OnAddFile(self, event=None):
                """Called to add a new file to the current Project"""
                newFile = self.GetNameToBeAdded()
                if not newFile:
                        return
                ext = os.path.splitext(newFile)[1]
                if ext.lower() not in _fileTypes:
                        wx.MessageBox('File %s is not of valid type!' % newFile,
                                _appName, wx.ICON_HAND)
                        return
                        
                t = self.tree
                sel = t.GetSelection()
                path, pageNum = t.GetPyData(sel)
                path = os.path.join(path, newFile)
                if os.path.exists(path):
                        if not os.path.isfile(path):
                                wx.MessageBox('%s already exists and is of the wrong type' % newFile,
                                        _appName, wx.ICON_HAND)
                                return
                else:
                        open(path, 'w').close() # empty file

                f = open(path)
                try:
                        data = f.read()
                finally:
                        f.close(); f = None
                self.AddFile(path, data)

        def OnAddDir(self, event=None):
                """Adds a new directory to the current Project"""
                newDir = self.GetNameToBeAdded()
                if not newDir:
                        return

                t = self.tree
                sel = t.GetSelection()
                path, pageNum = t.GetPyData(sel)
                path = os.path.join(path, newDir)
                if os.path.exists(path):
                        if not os.path.isdir(path):
                                wx.MessageBox('%s already exists and is not a directory' % newDir,
                                        _appName, wx.ICON_HAND)
                else:
                        os.mkdir(path)
                self.AddDirToTree(path)

        def GetNameToBeAdded(self):
                """Prompt user for a filename.
                This displays a message dialog asking the user from the name of a new file or directory.
                This name is then used to create the file.
		"""
                if _debug: pdb.set_trace()
                t = self.tree
                root = t.GetRootItem()
                defaults = self.GetFolder(_idxDefault)
                item = t.GetSelection()
                selIsValid = item.IsOk() and item != root and item != defaults \
                        and t.GetItemParent(item) != defaults
                if selIsValid:
                        path, pageNum = t.GetPyData(item)
                        selIsValid = (pageNum < 0) # i.e. item is a directory
                if not selIsValid:
                        wx.MessageBox("Invalid tree selection!", _appName, style=wx.ICON_ERROR)
                        return False
                
                entry = wx.TextEntryDialog(self, "Enter name of new file or directory", _appName)
                try:
                        if entry.ShowModal() == wx.ID_OK:
                                return entry.GetValue()
                finally:
                        entry.Destroy(); entry = None

        def OnLoadCode(self, evt=None):
                """Loads user code and resets the graph to new start-up config"""
                locks.CancelSim()
                self.SaveProject()
                self.LoadCode()
		# Enable packet logging in simulation
                self.OnResetGraph()
                self.EnableDisableForFiles()

        def OnResetGraph(self, evt=None):
                """Cancels simulation and returns graph to startup"""
                locks.CancelSim()
                self.canvas.ClearGraph()
                gc.collect()
                try:
                        import Dingo.Project.main as main
                        self.sim = self.FindSim(main)
                        if self.sim is None:
                                wx.MessageBox("No subclass of Simulation found!",
                                        _appName, wx.ICON_ERROR)
                                self.ClearOldCode()
                except:
			self.OnError()
                self.EnableDisableForFiles()
		# Network topology generation
		if self.isLoggingTop.IsChecked(): self.saveTopology()
		# Packet logging
		# TODO: refactor path finding code
		if self.isLoggingPackets.IsChecked():
                	root = self.tree.GetRootItem()
                	assert root is not None and root.IsOk()
                	rootPath = self.tree.GetPyData(root)
			if self.isLoggingPackets.IsChecked():
				self.sim.startPacketLogger(rootPath)
		return

        def OnStart(self, evt=None):
                """ Starts the currently loaded simulation."""
                if locks.SimRunning() or locks.SimPaused():
                        self.OnResetGraph()
                if self.sim is not None:
                        locks.RestartSim()
			if self.isLoggingPackets.IsChecked():
				self.sim.startPacketLogger()
                        self.sim.run()
                self.EnableDisableForFiles()

        def OnPause(self, evt=None):
                """ Pauses the currently loaded simulation"""
                locks.PauseSim()
		self.sim.stopPacketLogger()
                self.EnableDisableForFiles()

        def OnResume(self, evt=None):
                """ Resumes the currently loaded simulation"""
                locks.ResumeSim()
		if self.isLoggingPackets.IsChecked():
			self.sim.startPacketLogger()
                self.EnableDisableForFiles()

	def OnAttach(self, evt):
        	Publisher().sendMessage((consts.ATTACH_TOPIC), None)
		print 'GUI sent attach message'
		return

	def OnDetach(self, evt):
        	Publisher().sendMessage((consts.DETACH_TOPIC), None)
		print 'GUI sent detach message'
		return


        def OnCancel(self, evt=None):
                """ Cancels the currently loaded simulation"""
                locks.CancelSim()
		self.sim.stopPacketLogger()
                self.EnableDisableForFiles()

        def LoadCode(self):
                """Imports the user code from the Notebook"""
                locks.CancelSim()
                self.ClearOldCode()
                root = self.tree.GetRootItem()
                projDir = self.tree.GetPyData(root)

                imp.acquire_lock() # to prevent concurrent import issues
                try:
                        # ensure that Project package looks in right place for files
                        import Dingo.Project as Project
                        Project.__path__[:] = (projDir,)
                        try:
                                import Dingo.Project.main as main
                        except:
				self.OnError()
                finally:
                        imp.release_lock()
                self.EnableDisableForFiles()

	def OnError(self):
		ty, exc, tb = sys.exc_info()
                wx.MessageBox('%s(%s)' % (ty, exc.args))
                self.ClearOldCode()
		print exc
		print ty
		print tb
                raise ty, exc, tb


        def FindSim(self, main):
                """Find the Simulation subclass in the main Project file"""
                sim = None
                for t in main.__dict__.itervalues():
                        if isinstance(t, type) and issubclass(t, simulation.Simulation):
                                if sim is None:
                                        sim = t
                                else:
                                        wx.MessageBox("Duplicate Simulation subclasses: <%s, %s>" % (sim, t),
                                                _appName, wx.ICON_ERROR)
                if sim is None:
                        return None
                else:
			sim.projectDir = self.projectDir # PROJECT DIR
                        return sim(self.canvas)
                
        def ClearOldCode(self):
                """Remove old user code to make way for new version"""
                locks.CancelSim()

                try:
                        self.sim = None
                        imp.acquire_lock() # avoid concurrent update issues
                        try:
                                # remove modules from sys.modules dict (the import cache)
                                import Dingo.Project as Project
                                prefix = Project.__name__ + '.'
                                for modName in sys.modules.keys():
                                        if modName.startswith(prefix):
                                                del sys.modules[modName]
                        finally:
                                imp.release_lock()
                finally:
                        self.canvas.ClearGraph()
                        gc.collect()

        #---------------------------------------------
	def SaveFileInProjectDir(self, filename, filedata):
		"""
		Save a file in the project root directory.
		Note that os.getCwd() gives the path to the Dingo library itself.

		@type filename: str
		@param filename: Name of the file to write to
		@type filedata: str
		@param filedata: Data to write to the new file (in lines)
		@rtype None
		"""
                root = self.tree.GetRootItem()
                assert root is not None and root.IsOk()
                rootPath = self.tree.GetPyData(root)

		fh = open(rootPath + os.sep + filename, 'w')
		for line in filedata:
			fh.write(line + '\n')
		fh.close()
		return


        #---------------------------------------------
	# View menu methods

# 	def OnTopResize(self, event):
# 		### FIXME
# 		print 'OnTopResize... event:', event
# 		child = self.top_page.GetChildren()
# 		print 'children', child
# 		print '# children', len(child)
# 		if len(child) > 0:
# 			size = self.top_page.GetParent().GetClientSize()
# 			bmp = child[0].GetBitmap()
# #			bmp.Scale(size[0], size[1], 1.0)
# 		return

# 	def OnViewTopology(self, event):
# 		top_csv = os.sep + opj(self.projectDir + os.sep
# 				       + 'topology.csv')
# 		top_png = os.sep + opj(self.projectDir + os.sep
# 				       + 'topology.png')

# 		menuitem = self.mainmenu.FindItemById(event.GetId())
# 		if menuitem.IsChecked():
# 			if self.top_page is not None:
# 				self.top_page.Destroy()
# 				self.top_page = None

# 			self.saveTopology()
# 			self.status.SetStatusText('Please wait. This may ' +
# 						  'take some time...')

# 			os.system('dingo-top ' + top_csv)

# 			top_im = wx.Image(top_png, wx.BITMAP_TYPE_PNG)

# 	       		self.top_page = DingoPage(self.nb, wx.ID_ANY)
# 			self.top_page.Bind(wx.EVT_SIZE, self.OnTopResize)
# 			self.nb.AddPage(self.top_page, 'Network topology',
# 					select=True)
# 			size = self.nb.GetClientSize()			
#         		self.top_page.SetSize(size)
#         		self.top_page.SetSizer(self.nb.GetSizer())

# 			top_im = top_im.Scale(size[0], size[1], 1.0)
# 			top_bmp = top_im.ConvertToBitmap()

			
# 			wx.StaticBitmap(self.top_page, wx.ID_ANY, top_bmp,
# 					(0,0), top_bmp.GetSize())
# 			return
# 		else:
# 			if self.top_page is not None:
# 				self.top_page.Destroy()
# 				self.top_page = None
# 				return
# 			return
# 		return


        #---------------------------------------------
	# Network topology methods
	
	def OnRandomiseTopology(self, event):
		"""Randomise (Uniform) the network topology of the currently loaded Dingo app.
		See L{Dingo.simulation.Simulation}
		"""
		self.sim.randomiseTopology()
		self.status.SetStatusText('Now using random (uniform) network topology')
		# Network topology generation
		if self.isLoggingTop.IsChecked(): self.saveTopology()
		return

	def OnGaussianiseTopology(self, event):
		"""Randomise (Gaussian) the network topology of the currently loaded Dingo app.
		See L{Dingo.simulation.Simulation}
		"""
		self.sim.gaussianiseTopology()
		self.status.SetStatusText('Now using random (Gaussian) topology')
		# Network topology generation
		if self.isLoggingTop.IsChecked(): self.saveTopology()
		return
	
	def OnGridifyTopology(self, event):
		"""Gridify the network topology of the currently loaded Dingo app.
		See L{Dingo.simulation.Simulation}
		"""
		self.sim.gridifyTopology()
		self.status.SetStatusText('Now using grid network topology')
		# Network topology generation
		if self.isLoggingTop.IsChecked(): self.saveTopology()
		return

	def OnSaveTopology(self, event):
		"""Callback to save the current network topology in a CSV file.
		See L{Dingo.Main.DingoApp.saveTopology}
		"""
		self.saveTopology()
		return

	def saveTopology(self):
		"""Save the current network topology in a CSV file.
		Will use the filename C{topology.csv} in the project directory.
		See L{Dingo.simulation.Simulation}
		"""
		self.SaveFileInProjectDir('topology.csv',
					  self.sim.generateTopology())
		self.status.SetStatusText('Network topology saved in topology.csv')
		return

	def OnLoadTopology(self, event):
		"""Save the current network topology in a CSV file.
		Will use the filename C{topology.csv} in the project directory.
		See L{Dingo.simulation.Simulation}
		
		@precondition: File topology.csv exists in project directory.
		"""
		#filename = 'topology.csv'
                root = self.tree.GetRootItem()
                assert root is not None and root.IsOk()
                rootPath = self.tree.GetPyData(root)

                dlg = wx.FileDialog(self,
				    message='Open network topology',
				    defaultFile='topology.csv',
				    defaultDir=rootPath,
				    style=wx.OPEN)
                if dlg.ShowModal() != wx.ID_OK:
			return
	        filename = os.path.abspath(dlg.GetPath())
		dlg.Destroy()
		
		
		try:
			fh = open(filename, 'r')
		except:
			self.status.SetStatusText(filename + ' not found.')
			return
		topology = fh.readlines()
		fh.close()
		self.sim.loadTopology(topology)
		self.status.SetStatusText('Network topology loaded from file '
					  + filename)
		return

        #---------------------------------------------
	# Logging methods

	def OnPacketLoggingToggle(self, event):
		"""Toggle logging of each packet sent in the network during simulation execution.
		See L{Dingo.simulation.PacketLogger}
		"""
		if self.isLoggingPackets.IsChecked():
			self.sim.startPacketLogger()
		else:
			self.sim.stopPacketLogger()
		return

        #---------------------------------------------
	# Plugin methods

	def OnActivateAllPlugins(self, event):
		"""Activate all available Dingo plugins.
		See L{Dingo.pluginManager.PluginManager.activateAllPlugins}
		"""
		for menuitem in self.pluginMenu.GetMenuItems():
			if menuitem.IsCheckable():
				menuitem.Check(True)
		self.pluginManager.activateAllPlugins()
		return

	def OnDeactivateAllPlugins(self, event):
		"""Deactivate all available Dingo plugins.
		See L{Dingo.pluginManager.PluginManager.deactivateAllPlugins}
		"""
		for menuitem in self.pluginMenu.GetMenuItems():
			if menuitem.IsCheckable():
				menuitem.Check(False)
		self.pluginManager.deactivateAllPlugins()
		return

	def OnPluginToggle(self, event):
		"""Toggle on particular Dingo plugin.
		See L{Dingo.pluginManager.PluginManager.activatePlugins}
		and L{Dingo.pluginManager.PluginManager.deactivatePlugins}
		"""
		menuitem = self.mainmenu.FindItemById(event.GetId())
		plugin = menuitem.GetLabel()
		if menuitem.IsChecked():
			self.pluginManager.activatePlugin(plugin)
		else:
			self.pluginManager.deactivatePlugin(plugin)
		return

        #---------------------------------------------
	# Help methods
	
        def OnHelpAbout(self, event):
                """Displays the UI about box

		See L{Dingo.about.MyAboutBox}
		"""
                from Dingo.about import MyAboutBox
                about = MyAboutBox(self)
                about.Destroy()

        def OnHelpContents(self, event):
                """Display help contents"""
                if self.GetHelpWindow(): self.help.DisplayContents()

        def OnHelpIndex(self, event):
                """Display help Index"""
                if self.GetHelpWindow(): self.help.DisplayIndex()

        def GetHelpWindow(self):
                """Creates (if needed) and returns the Help window"""
                if not self.help:
                        try:
                                self.help = html.HtmlHelpController()
                                helpPath = os.path.join(Dingo.__path__[0], 'docs', 'dingo.hhp')
                                self.help.AddBook(helpPath)
                        except:
                                wx.MessageBox(`(sys.exc_info()[0],) + sys.exc_info()[1].args`)
                                self.help = None
                return self.help

        #---------------------------------------------
        def OnCloseWindow(self, event):
                self.dying = True
                self.window = None
                self.mainmenu = None
                try:
                        self.ClearOldCode()
                finally:
                        self.Destroy()

        #---------------------------------------------
        def ShowTip(self):
                """Show tool tips.
                These are taken from the ~tips/showTips text file"""
                try:
                        showTipText = open(opj("tips/showTips")).read()
                        showTip, index = eval(showTipText)
                except IOError:
                        showTip, index = (1, 0)
                if showTip:
                        tp = wx.CreateFileTipProvider(opj("tips/tips.txt"), index)
                        showTip = wx.ShowTip(self, tp)
                        index = tp.GetCurrentTip()
                        open(opj("tips/showTips"), "w").write(str((showTip, index)))


        #---------------------------------------------
        def OnTaskBarActivate(self, evt):
                if self.IsIconized():
                        self.Iconize(False)
                if not self.IsShown():
                        self.Show(True)
                self.Raise()

        #---------------------------------------------

        TBMENU_RESTORE = 1000
        TBMENU_CLOSE   = 1001

        def OnTaskBarMenu(self, evt):
                menu = wx.Menu()
                menu.Append(self.TBMENU_RESTORE, "Restore wxPython Demo")
                menu.Append(self.TBMENU_CLOSE,   "Close")
                self.tbicon.PopupMenu(menu)
                menu.Destroy()

        #---------------------------------------------
        def OnTaskBarClose(self, evt):
                self.Close()
                # because of the way wx.TaskBarIcon.PopupMenu is implemented we have to
                # prod the main idle handler a bit to get the window to actually close
                wx.GetApp().ProcessIdle()


        #---------------------------------------------
        def OnIconfiy(self, evt):
                evt.Skip()

        #---------------------------------------------
        def OnMaximize(self, evt):
                evt.Skip()



class DingoPage(wx.NotebookPage):
	"""Notebook page in the Dingo GUI.
	Extends ex.NotebookPage to define methods that are called on
	all Dingo notebook pages.
	"""
    	def __init__(self, parent, id):
		wx.NotebookPage.__init__(self, parent, id)
        	return
    	def CanPaste(self):
        	return False
    	def GetSelectedText(self):
        	return None



class MySplashScreen(wx.SplashScreen):
    """Splash screen widget.
    From U{http://wiki.wxpython.org/SplashScreen}.
    """
    def __init__(self, parent=None):
        # This is a recipe to a the screen.
        # Modify the following variables as necessary.
	myfilename = (Dingo.Main.__file__).split(os.sep)
	mydir = myfilename[:len(myfilename)-1]
        imagefile = string.join(mydir, os.sep) + '/images/splash.png'

        aBitmap = wx.Image(imagefile, wx.BITMAP_TYPE_PNG).ConvertToBitmap()
        splashStyle = wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT
        splashDuration = 2000 # milliseconds
        # Call the constructor with the above arguments in exactly the
        # following order.
        wx.SplashScreen.__init__(self, aBitmap, splashStyle,
                                 splashDuration, parent)
        self.Bind(wx.EVT_CLOSE, self.OnExit)
        wx.Yield()
	
    def OnExit(self, evt):
        self.Hide()
        evt.Skip()
	return
        
class MyApp(wx.App):
	"""Wrapper to load splash screen and Dingo GUI.
	"""
    	def OnInit(self):
        	MySplash = MySplashScreen()
        	MySplash.Show()
		frame = DingoApp(None, -1, "Dingo development tool for wireless sensor networks")
		MySplash = None
		frame.Show(True)
        	return True

def _Run(loadFile):
        """Called from Dingo_Main to run app.  
        This creates the application then calls the code to populate and run the GUI.
        """
        global _file
        _file = loadFile
	import gc
	gc.set_debug(gc.DEBUG_LEAK)
        app = MyApp(0)
        app.MainLoop()
	return

if __name__ == '__main__':
         global _file
         _file = loadFile
